// ==UserScript==
// @name        Ci Helper for GitCode
// @namespace   Violentmonkey Scripts
// @match       https://gitcode.com/*
// @version     1.1.10
// @author      milkpotatoes
// @require     ../utils/dialog.js
// @require     ../utils/utils.js
// @require     ./ci-helper-buttons.js
// @homepageURL https://gitee.com/milkpotatoes/tools-for-openharmony-repo
// @description Shortcuts for trigger Openharmony ci GGWatch and some others
//
// Permissions
// permissions to request user script API
// @grant       GM.getResourceUrl
// @grant       GM.getResourceText
// @grant       GM.deleteValues
// @grant       GM.getValue
// @grant       GM.setValue
// @grant       GM.registerMenuCommand
//
// Resources
// stylesheet for ci helper
// @resource    CI_HELPER_STYLE           ci-helper-style.css
// stylesheets under here
// @resource    MD_ICON_ALL_CSS           https://unpkg.com/material-icons@1.13.12/iconfont/material-icons.css
// @resource    MD_ICON_FILLED_CSS        https://unpkg.com/material-icons@1.13.12/iconfont/filled.css
// @resource    MD_ICON_OUTLINED_CSS      https://unpkg.com/material-icons@1.13.12/iconfont/outlined.css
// @resource    MD_ICON_ROUND_CSS         https://unpkg.com/material-icons@1.13.12/iconfont/round.css
// @resource    MD_ICON_SHARP_CSS         https://unpkg.com/material-icons@1.13.12/iconfont/sharp.css
// @resource    MD_ICON_TWO_TONE_CSS      https://unpkg.com/material-icons@1.13.12/iconfont/two-tone.css
// woff fonts under here
// @resource    MD_ICON_FILLED_WOFF       https://unpkg.com/material-icons@1.13.12/iconfont/material-icons.woff
// @resource    MD_ICON_OUTLINED_WOFF     https://unpkg.com/material-icons@1.13.12/iconfont/material-icons-outlined.woff
// @resource    MD_ICON_ROUND_WOFF        https://unpkg.com/material-icons@1.13.12/iconfont/material-icons-round.woff
// @resource    MD_ICON_SHARP_WOFF        https://unpkg.com/material-icons@1.13.12/iconfont/material-icons-sharp.woff
// @resource    MD_ICON_TWO_TONE_WOFF     https://unpkg.com/material-icons@1.13.12/iconfont/material-icons-two-tone.woff
// woff2 fonts under here
// @resource    MD_ICON_FILLED_WOFF2      https://unpkg.com/material-icons@1.13.12/iconfont/material-icons.woff2
// @resource    MD_ICON_OUTLINED_WOFF2    https://unpkg.com/material-icons@1.13.12/iconfont/material-icons-outlined.woff2
// @resource    MD_ICON_ROUND_WOFF2       https://unpkg.com/material-icons@1.13.12/iconfont/material-icons-round.woff2
// @resource    MD_ICON_SHARP_WOFF2       https://unpkg.com/material-icons@1.13.12/iconfont/material-icons-sharp.woff2
// @resource    MD_ICON_TWO_TONE_WOFF2    https://unpkg.com/material-icons@1.13.12/iconfont/material-icons-two-tone.woff2
// ==/UserScript==


'use strict';

class GitCodeUtils extends PrUtils {
    static isLogin() {
        return localStorage.getItem('userInfo') !== undefined;
    }

    static getPRAuthor() {
        try {
            return document.querySelector('.g-discussion-header-info>a').getAttribute('href').substring(1);
        } catch {
            const stateCard = document.querySelector('.g-issue-description');
            const chd = stateCard.lastElementChild.lastElementChild.children;
            for (const e of chd) {
                if (e.classList.contains('text-[var(--devui-aide-text)]')) {
                    return e.textContent;
                }
            }
        }
        return '';
    }

    static getLoginUser() {
        const userInfo = JSON.parse(localStorage.getItem('userInfo') ?? null);
        if (userInfo === null) return null;
        return userInfo.username;
    }

    static getWebLang() {
        return localStorage.getItem('gitcode_lang') ?? navigator.language;
    }

    static getPRState() {
        const stateCard = document.querySelector('.g-issue-description');
        let prState = Utils.PR_STATE.UNKNOWN;
        switch (stateCard.firstElementChild.firstElementChild.textContent) {
            case '已关闭':
            case 'Closed':
                prState |= Utils.PR_STATE.CLOSED;
                break;
            case '已合并':
            case 'Merged':
                prState |= Utils.PR_STATE.MERGED;
                break;
            case '已开启':
            case 'Opened':
                prState |= Utils.PR_STATE.OPEN;
                break;
            default:
                break;
        }

        if (stateCard.querySelector('[title="conflicted"') !== null) {
            prState |= Utils.PR_STATE.CONFLICTED
        }

        if (stateCard.children[1].firstElementChild.firstElementChild.title.startsWith('[WIP]')) {
            prState |= Utils.PR_STATE.DRAFT;
        }

        return prState;
    }

    static getPRPage() {
        const page = location.pathname.split('/').pop();
        const prId = RegExp(/\d+/).exec(page);
        switch (page) {
            case 'files':
                return Utils.PR_PAGE.FILES;
            case 'checks':
                return Utils.PR_PAGE.CHECKS;
            case 'commits':
                return Utils.PR_PAGE.COMMITS;
            default:
                if (prId && prId[0].length === page.length) {
                    return Utils.PR_PAGE.COMMENTS;
                } else {
                    return Utils.PR_PAGE.UNKNOWN;
                }
        }
    }
    static bind() {
        Utils.bindPrUtils(GitCodeUtils);
    }
}
GitCodeUtils.bind();

const CI_HELPER_USER_BUTTONS = 'CI_HELPER_USER_BUTTONS';
const CI_OH_PR_PAGE_MATCHER = /openharmony\/\w+\/pull\//g;

class GitCodeHelperUtils extends HelperUtils {
    getCommentEditor() {
        const commentContainer = document.querySelector('.g-discussion-item .dp-md-editor');
        if (!commentContainer) {
            return null;
        }
        return commentContainer.children[1].firstElementChild.firstElementChild;
    }

    /**
     * 
     * @param {Event} e 
     * @param {string?} message 
     * @returns 
     */
    triggerButtonComment(e, message) {
        const target = e.handleTarget ?? e.target;
        const msg = message ?? target.getAttribute('data');
        if (msg === '' || msg === undefined || msg === null) {
            throw new TypeError('invalid arguments, message cannot be empty.')
        }
        const editor = this.getCommentEditor();
        if (!editor) {
            return;
        }
        let btnContainer = document.querySelector('.g-discussion-item .edit-area_actions');
        // empty input
        btnContainer.firstElementChild.lastElementChild.click();

        editor.dispatchEvent(new Event('focus'));
        editor.value = msg;
        editor.dispatchEvent(new Event('input'));
        setTimeout(() => {
            btnContainer.firstElementChild.firstElementChild.click();
        }, 100);
    }

    switchToComment(_) {
        document.querySelector('#mr-tabs [data-toggle="discuss"]').click();
    }

    goToComment(_) {
        document.documentElement.scrollTo({
            left: document.documentElement.scrollLeft,
            top: document.body.scrollHeight - document.body.offsetHeight,
            behavior: "smooth"
        });
    }
    openLoginDialog(_) {
        document.querySelector('.g-toolbar-right').querySelectorAll('button')[0].click();
    }
}
InitHelperUtils(new GitCodeHelperUtils());

const setButtonStyle = (button, styles) => {
    const CSS_TAG = 'css:';
    styles.forEach(style => {
        if (style.startsWith(CSS_TAG)) {
            button.style.cssText += style.substring(CSS_TAG.length) + (style.endsWith(';') ? '' : ';');
        } else {
            button.classList.add(style);
        }
    });
}

/**
 * @type {HTMLElement}
 */
let g_toolbar = null;
/**
 * @type {HTMLElement}
 */
let g_popup = null;
/**
 * @type {ShadowRoot}
 */
let g_shadowRoot = null;
/**
 * @type {ToolbarStyle}
 */
let g_toolbarStyle = null;
let g_popupHideTimeout = 0;
let g_popupShowAnimate = null;
let g_popupHideAnimate = null;

const bindPopup = (button) => {
    let tip = '';
    if (g_popupShowAnimate === null) {
        g_popupShowAnimate = g_popup.animate([{ top: '50%', opacity: '0' }, {}],
            { easing: 'ease-out', duration: 200, pause: true, });
        g_popupShowAnimate.cancel();
    }
    if (g_popupHideAnimate === null) {
        g_popupHideAnimate = g_popup.animate([{}, { opacity: '0' }],
            { easing: 'ease-in', duration: 200, });
        g_popupHideAnimate.cancel();
    }

    button.addEventListener('mouseenter', (e) => {
        clearTimeout(g_popupHideTimeout);
        tip = e.target.title !== '' ? e.target.title : tip;
        if (tip === '') return;
        e.target.title = '';
        g_popup.style.display = 'block';
        const box = e.target.getBoundingClientRect();
        g_popup.style.left = `${box.x + box.width / 2 - g_toolbar.getBoundingClientRect().x}px`;
        g_popupHideAnimate.cancel();
        g_popupShowAnimate.play();
        g_popup.textContent = tip;
        g_popup.style.opacity = '';
    });
    button.addEventListener('mouseleave', (e) => {
        clearTimeout(g_popupHideTimeout);
        if (tip === '') return;
        e.target.title = tip;
        g_popupHideAnimate.play();
        g_popupShowAnimate.cancel();
        g_popup.style.opacity = 0;
        g_popupHideTimeout = setTimeout(() => {
            g_popup.style.display = '';
        }, 200);
    });
}
/**
 * 
 * @param {ShadowRoot} shadowRoot 
*/
const initTools = async (shadowRoot) => {
    g_toolbar.style.display = '';
    helpBtns.forEach((btnInfo) => {
        if (!Utils.getButtonEnabled(btnInfo)) {
            return;
        }
        const btn = genHelperButton(btnInfo);
        bindPopup(btn);
        shadowRoot.append(btn);
    });
    const btns = JSON.parse(await GM.getValue(CI_HELPER_USER_BUTTONS, '[]'));
    btns.forEach(async btnInfo => {
        try {

            if (!Utils.getButtonEnabled(btnInfo)) {
                return;
            }
            const btn = genUserButton(btnInfo);
            insertUserButton(shadowRoot, btn, btnInfo.position ?? 'end');
            bindPopup(btn);
        } catch (e) {
            console.error(`Failed to add custom button: ${btnInfo.name}`, e);
            await AlertDialog.alert(`Failed to add custom button: ${btnInfo.name}`, e.message);
        }
    });
    shadowRoot.firstElementChild.before(g_popup);
    document.body.style.paddingBottom = '96px';
}

/**
 * @param {ShadowRoot} shadowRoot
 */
const deinitTools = (shadowRoot) => {
    g_toolbar.style.display = 'none';
    g_existsButtonId.clear();
    shadowRoot.querySelectorAll('button').forEach(e => {
        e.remove();
    });
}

const getGitCodeCommentBarHeight = () => {
    const bar = document.querySelector('.add-discussion-layout');
    if (bar == null) return 0;
    return bar.getBoundingClientRect().height;
}

class ToolbarStyle {
    constructor(bar) {
        this.toolbar = bar;
    }
    float(height = getGitCodeCommentBarHeight()) {
        this.toolbar.animate([
            { marginBottom: this.toolbar.style.marginBottom },
            { marginBottom: `calc(${height}px)` },
        ], ToolbarStyle.animateTiming);
        this.toolbar.style.marginBottom = `calc(${height}px)`;
        this.floating = true;
    }
    adsorb() {
        this.toolbar.animate([
            { marginBottom: this.toolbar.style.marginBottom },
            { marginBottom: '0em' },
        ], ToolbarStyle.animateTiming);
        this.toolbar.style.marginBottom = '';
        this.floating = false;
    }
    handle() {
        const currentScrollTop = document.documentElement.scrollTop;
        const scrollDown = currentScrollTop - this.lastScrollTop > 0;
        const disbarHeight = getGitCodeCommentBarHeight();
        if (scrollDown) {
            if (disbarHeight === 0) {
                if (this.floating) {
                    this.adsorb();
                }
            } else {
                if (!this.floating) {
                    this.float(disbarHeight);
                }
            }
        } else {
            if (disbarHeight > 0 && !this.floating) {
                this.float(disbarHeight);
            }
        }
    }
    register() {
        if (this.registered) return;
        document.addEventListener('scroll', () => { this.handle() });
        this.registered = true;
        document.addEventListener('click', (e) => {
            if (e.target.closest('.add-discussion-layout')) {
                this.float();
            } else {
                if (this.floating) this.float();
            }
        });
    }
    static animateTiming = { easing: 'ease-in', duration: 200, };
    toolbar;
    floating = false;
    lastScrollTop = 0;
    registered = false;
}

const makeFloat = () => {
    if (g_toolbarStyle === null) {
        g_toolbarStyle = new ToolbarStyle(g_toolbar);
    }
    if (Utils.prUtils.getPRPage() === Utils.PR_PAGE.COMMENTS) {
        setTimeout(() => {
            g_toolbarStyle.float();
        }, 500);
    } else {
        g_toolbarStyle.adsorb();
    }
}

const reinitWithRetry = async (shadowRoot, retry = 0, timeout) => {
    deinitTools(shadowRoot);
    try {
        await initTools(shadowRoot);
        makeFloat();
    } catch (error) {
        deinitTools(shadowRoot);
        if (retry <= 0) {
            if (location.href.match(CI_OH_PR_PAGE_MATCHER) !== null) {
                AlertDialog.alert('Failed to init toolbar.', error.message);
                console.error(error);
            }
            return;
        }
        setTimeout(reinitWithRetry, timeout, shadowRoot, retry - 1, timeout);
    }
}

const main = (retry) => {
    const prCard = document.querySelector('.g-issue-description');
    if (prCard === null) {
        if (retry > 0) {
            setTimeout(main, 1000, retry - 1);
        }
        return;
    }

    const toolbar = document.createElement('div');
    g_toolbar = toolbar;
    const popup = document.createElement('span');
    popup.classList.add('ci-helper-popup');
    g_popup = popup;

    toolbar.classList.add('ci-helper-toolbar');
    document.body.append(toolbar);
    document.querySelector('#app').firstElementChild.style.paddingBottom = '72px';

    const shadowRoot = toolbar.attachShadow({ mode: 'closed' });
    g_shadowRoot = shadowRoot;

    MaterialIcon.getMDIconCss(MaterialIcon.ICON_TYPE.FILLED)
        .then((styles) => {
            styles.apply(shadowRoot);
        });

    const styleSheet = new CSSStyleSheet();
    const css = GM.getResourceText('CI_HELPER_STYLE');
    if (typeof css === 'string') {
        styleSheet.replace(css)
            .then(e => {
                shadowRoot.adoptedStyleSheets.push(e);
            });
    } else {
        css.then(style => {
            styleSheet.replace(style)
                .then(e => {
                    shadowRoot.adoptedStyleSheets.push(e);
                });
        })
    }

    initTools(shadowRoot);

    g_toolbarStyle = new ToolbarStyle(toolbar);
    const reinit = async (e) => {
        const { current: to } = e.data[0];
        if (to.match(CI_OH_PR_PAGE_MATCHER) === null) {
            deinitTools(shadowRoot);
            return;
        }
        reinitWithRetry(shadowRoot, 5, 1000);
    }
    // for pushState call
    Utils.getHistoryEventTarget().addEventListener('pushstate', reinit);
    // for user forward or back
    window.addEventListener('popstate', e => {
        e.data = [{ current: location.href }];
        reinit(e);
    });

    toolbar.animate([
        { transform: 'translate(calc(-50% - 1em), 100%)' },
        { transform: 'translate(calc(-50% - 1em), 0)' },
    ], {
        easing: 'ease-in',
        duration: 500,
    });

    if (Utils.prUtils.getPRPage() === Utils.PR_PAGE.COMMENTS) {
        let timer = setInterval(() => {
            const height = getGitCodeCommentBarHeight();
            if (height > 0) {
                clearInterval(timer);
                g_toolbarStyle.float();
            }
        }, 500);
    }
    g_toolbarStyle.register();
    return toolbar;
};

{
    const addCustomButtonLabel = {
        'default': '@en',
        'zh-CN': '添加自定义按钮',
        'en': 'Add custom button',
    };
    const resetPermissionLabel = {
        'default': '@en',
        'zh-CN': '重置授权设置',
        'en': 'Reset permissions',
    };
    GM.registerMenuCommand(Utils.getI18NRes(addCustomButtonLabel), async () => {
        const val = await GM.getValue(CI_HELPER_USER_BUTTONS, '');
        const textArea = document.createElement('textarea');
        textArea.value = val;
        textArea.style.cssText = 'width: 100%;min-height:16em;resize:vertical;background:rgba(0 0 0 / .05);border:none;' +
            'border-radius:.5em;padding:1em;box-sizing:border-box;overflow:auto';
        textArea.placeholder = `Add custom buttons on option bar.
For example:
[{
    "name": "open-workspace",
    "label": {"default": "open_in_new"},
    "style": ["material-icons", "transparent"],
    "enable": ["arkcompiler_*", "page:COMMENTS"],
    "events": {
        "click": "open /"
    }
}]`;
        textArea.addEventListener('keydown', (e) => {
            e.stopPropagation();
            if (e.key === 'Tab' && !e.shiftKey) {
                const start = textArea.selectionStart;
                if (start === textArea.selectionEnd) {
                    const value = textArea.value;
                    const newText = value.substring(0, start) + '  ' + value.substring(start);
                    textArea.value = newText;
                    textArea.setSelectionRange(start + 2, start + 2);
                }
                e.preventDefault();
            }
        });
        const dialog = new AlertDialog()
            .setTitle(Utils.getI18NRes(addCustomButtonLabel))
            .setView(textArea)
            .setModal(true)
            .setWidth(500)
            .setIcon('close', { dismissOnClick: true })
            .setHeaderButton(Utils.getI18NRes({
                'default': '@en',
                'zh-CN': '保存',
                'en': 'save',
            }), {
                dismissOnClick: false, events: {
                    click: () => {
                        try {
                            const conf = JSON.parse(textArea.value);
                            conf.forEach(e => {
                                if (e.name === undefined || e.name === null || e.name === '') {
                                    throw TypeError('button name cannot be empty');
                                }
                            });
                            GM.setValue(CI_HELPER_USER_BUTTONS, textArea.value);
                            dialog.dismiss();
                            reinitWithRetry(g_shadowRoot, 0, 0);
                        } catch (e) {
                            console.error(e);
                            AlertDialog.alert('failed to save custom buttons', e.message);
                        }
                    }
                }
            });
        dialog.show();
    });
    GM.registerMenuCommand(Utils.getI18NRes(resetPermissionLabel), async () => {
        if (await AlertDialog.confirm(`${Utils.getI18NRes(resetPermissionLabel)}?`, '')) {
            GM.deleteValues([
                JUMP_WHITELIST_KEY,
                JUMP_BLACKLIST_KEY,
                OPEN_WHITELIST_KEY,
                OPEN_BLACKLIST_KEY,
            ]);
        }
    });
}
Utils.hookHistoryMethod();

if (location.href.match(CI_OH_PR_PAGE_MATCHER) !== null) {
    setTimeout(main, 2000, 10);
} else {
    let initd = false;
    const pageMatcher = async (e) => {
        if (initd) return;
        const { current: to } = e.data[0];
        if (to.match(CI_OH_PR_PAGE_MATCHER) !== null) {
            initd = true;
            setTimeout(main, 1000, 10);
            Utils.getHistoryEventTarget().removeEventListener('pushstate', pageMatcher);
            return;
        }
    }
    const popStateMatcher = (e) => {
        const to = location.href;
        e.data = [{ current: to }];
        if (to.match(CI_OH_PR_PAGE_MATCHER) !== null) {
            pageMatcher(e);
            window.removeEventListener('popstate', popStateMatcher);
        }
    }

    Utils.getHistoryEventTarget().addEventListener('pushstate', pageMatcher);
    window.addEventListener('popstate', popStateMatcher);
}
